/*
    Speedbump aka Project Kimbula
    Copyright (C) 2010  Ping Shin Ching

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see http://www.gnu.org/licenses.

	Ping Shin Ching
	righteous.ninja@gmail.com OR ping_sc@hotmail.com
	More information at http://www.tidytutorials.com/p/speedbump.html
 */

/*
	TAKE EXTRA CARE WHEN MODIFYING THIS FILE. ERRORS WILL TAKE DAYS TO DEBUG
*/


#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <resolv.h>
#include <sys/socket.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <netinet/in.h>

#include "helpers.h"
#include "application.h"
#include "defaultcontenthandler.h"
#include "chunkedcontenthandler.h"
#include "protocolhttphandler.h"

#include "httphandler.h"
#include "deflate.h"


#define logdhttp logd<<"HTTP("<<(name==HANDLER_UPSTREAM?"UP":"DN")<<(connectmethod?"|S":"")<<"): "

/*************************************************************************
 * Prepare and initialize the data handler
 *************************************************************************/
HTTPHandler::HTTPHandler(int id){
	int i;
	buffersize=0;
	buffer=0;
	header_finished=0;
	connectmethod=0;
	processedbytes=0;
	redirecturl=0;
	host_port=0;
	host_address=0;
	name=id;
	dirtybit=0;
	fullforward=0;
	cascadeon=0;

	chitf=0;

	for(i=0;i< HANDLER_TICKS;i++){
		handlerticks[i]=-1;
	}
}


/*************************************************************************
 * Called to process the data read from the socket
 *************************************************************************/
HANDLER_RESPONSE HTTPHandler::HandleData( char* inbuf, int* inbuflen){

	unsigned char buffermaxed = 0;
	char * rnrn;
	const char* c;
	HANDLER_RESPONSE resp;
	int headerbefore;

	ProtocolHTTPParams* php = (ProtocolHTTPParams*)param1; 

	if( (buffer = (char*)realloc(buffer, buffersize+sizeof(char)*(*inbuflen)))!=0){
		
		memcpy(buffer+buffersize, inbuf, (*inbuflen));
		buffersize+=sizeof(char)*(*inbuflen);
		
		processedbytes+=*inbuflen;
		
		ratehandler.CalculateRate(*inbuflen, processedbytes, ratelimit, rateadjustment);

		if(buffersize > MAX_HANDLER_BUFFER_SIZE){
			buffermaxed = 1;
			logw<<"HTTP: Buffer breached "<<MAX_HANDLER_BUFFER_SIZE<<lend; 
		}
	
		if(header_finished || fullforward){
			if(!chitf){
				if (buffermaxed)
					return HANDLER_FULL;
				else
					return HANDLER_FINISHED;
			}
			else{
				if(!connectmethod){
					switch(resp = chitf->HandleData(&buffer, &buffersize)){
						case HANDLER_MORE:
							break;
						case HANDLER_FINISHED:
							break;
						default:
							break;
					}
					cleanupBuffers();
					return resp;
				}
				else{
					logdhttp<<"connect method"<<lend;
					return HANDLER_FINISHED;
				}
			}
		}
		else{
			if (buffermaxed){
				if(buffer!=0){
					cleanupBuffers();
					if(chitf){
						delete chitf;
						chitf=0;
					}
				}
				return HANDLER_ERROR;
			}

			if((rnrn=strnstrn(buffer, buffersize, "\r\n\r\n", strlen("\r\n\r\n")))!=0 ){
				if(iheaderd.DecodeHeaders(buffer, rnrn-buffer+strlen("\r\n"))!=0 && (headerbefore=iheaderd.SerializedHeaderSize())>0){
					logdhttp<<"headers decoded "<< iheaderd.GetRequestLine()<<lend;			

					ModifyHeaders(&iheaderd);

					//Recut the requestline if full proxy
					if(!cascadeon && name==HANDLER_UPSTREAM)
						iheaderd.RecutRequestLine();

					header_finished=1;

					if(name==HANDLER_UPSTREAM)
						php->capi->PreRequestCAP(capi_param1, &iheaderd);

					if(iheaderd.IsConnect()){
						connectmethod=1;
					}

					FillHostAndPort(&iheaderd);

					if(connectmethod){
						logdhttp<<"connect method"<<lend;
						return HANDLER_FINISHED;
					}

					
					//resize the buffer down to the content minus the headers
					if(rnrn-buffer+strlen("\r\n\r\n")<buffersize){
						int l = buffersize-(rnrn-buffer+strlen("\r\n\r\n"));
						memmove(buffer, rnrn+strlen("\r\n\r\n"), l);
						buffer = (char*)realloc(buffer, sizeof(char)*l);
						buffersize=l;
					}
					else{
						logdhttp<<"no body headers only"<<lend;
						//no body - headers only
						cleanupBuffers();
					}
					
					if((c=iheaderd.GetHeader("Transfer-Encoding")) && !strcasecmp(c,"chunked")){
						/*Chunked*/
						//getIntermediateRequest();				
						//return HANDLER_FINISHED;	
						
						if(chitf==0){
							logdhttp<<"new chunked content handler"<<lend;
							chitf = new chunkedcontenthandler();
							chitf->HandleHeaders(&iheaderd);
						}
					}
					else{
						if(chitf==0){
							logdhttp<<"new default content handler"<<lend;
							chitf = new defaultcontenthandler();
							chitf->HandleHeaders(&iheaderd);
						}
					}
					
					switch(resp=chitf->HandleData(&buffer, &buffersize)){
						case HANDLER_MORE:
							break;
						case HANDLER_FINISHED:
							break;
						default:
							break;
					}
					cleanupBuffers();
					return resp;	
				}
				else{
					loge<<"Could not decode header"<<lend;
					return HANDLER_ERROR;
				}
			}
			else{
				return HANDLER_MORE;
			}
		}
	}
	else{
		cleanupBuffers();
		return HANDLER_ERROR;
	}
}


/*************************************************************************
 * Reset the header capture for when multiple requests are sent via the 
 * same socket
 *************************************************************************/
void HTTPHandler::ResetHeaderCapture(){
	logdhttp<<"Resetting header capture"<<lend; 
	if(!connectmethod){
		logdhttp<<"Reset header capture"<<lend; 
		header_finished=0;
	}
	iheaderd.FreeHeaders();
	if(chitf){
		delete chitf;
		chitf = 0;
	}
}


/*************************************************************************
 * Called to copy the processed data for sending
 *************************************************************************/
HANDLER_RESPONSE HTTPHandler::GetHandledData(char* outbuf, int* outbuflen){
	char* out;
	unsigned int outsize;
	const char* gzip;
	unsigned int  total, totalh;
	char * x;
	int deflateret;

	char tmps64[64];
	ProtocolHTTPParams* php;

	//printchararray(buffer, buffersize, "NIIIIIINNNJAA");
	if(chitf){
		php = (ProtocolHTTPParams*)param1; 
		switch(chitf->GetHandledData(&buffer, &buffersize)){
			case HANDLER_COPOUT:
				logdhttp<<"H-copout"<<lend;
				if(name==HANDLER_UPSTREAM){
					php->capi->RequestCAP(capi_param1, &buffer, &buffersize, &iheaderd);
				}else{
					php->capi->ResultCAP(capi_param1, &buffer, &buffersize, &iheaderd);
				}

				totalh=iheaderd.SerializedHeaderSize()+strlen("\r\n");
				total=buffersize+totalh;
				x = ((char*)realloc(buffer, sizeof(char)*total));
				memmove(x+totalh, x, buffersize);
				iheaderd.SerializeHeaders(x, &totalh);
				memcpy(x+totalh-strlen("\r\n"), "\r\n", strlen("\r\n"));
				buffer=x;
				buffersize=total;
				break;
			case HANDLER_FINISHED:
				logdhttp<<"H-finished"<<lend;
				gzip = iheaderd.GetHeader("Content-Encoding");
				if(gzip && strcmp(gzip, "gzip")==0){
					logd<<"HTTP:GZ: Content gzip encoded"<<lend;
					out = (char*)malloc(5000000);
					outsize=5000000;
					if((deflateret=http_inflate(buffer, &buffersize, out, &outsize, 0))==0){
						iheaderd.RemoveHeader("Content-Encoding");
						logd<<"HTTP:GZ: Inflate succeeded "<<outsize<<lend;
						logd<<"HTTP:GZ: XXX "<<buffersize<<" -> "<<outsize<<"  "<<outsize/buffersize<<"  "<< iheaderd.GetHeader("Content-type")<<lend;
						x = (char*)realloc(buffer, sizeof(char)*outsize);
						memcpy(x,out, outsize);
						buffer=x;
						buffersize=outsize;
						snprintf(tmps64, 64-1, "%d", buffersize); 
						iheaderd.AddHeader("Content-Length", tmps64);
						//printchararray(buffer, buffersize, "))))))))))))))))");
					}
					else{
						logw<<"HTTP:GZ: Inflate failed "<<deflateret<<lend;
					}
					free(out);
				}
				else{
				}
				
				if(iheaderd.GetHeader("content-type")){
				       	//&& strstr(iheaderd.GetHeader("content-type"), "text/html")){
					/*
					printf("*******************************%s\n", iheaderd.GetHeader("content-type"));
				       	if(strstr(iheaderd.GetHeader("content-type"), "text/html")){
						iheaderd.DisplayHeaders();
						printf("******************************* !!!!!!!!!\n");
					}
					*/
				}

				if(name==HANDLER_UPSTREAM){
					logdhttp<<"Called requestcap "<<buffersize<<lend;
					php->capi->RequestCAP(capi_param1, &buffer, &buffersize, &iheaderd);

				}else{
					logdhttp<<"Called resultcap "<<buffersize<<lend;
					php->capi->ResultCAP(capi_param1, &buffer, &buffersize, &iheaderd);
				}

				totalh=iheaderd.SerializedHeaderSize()+strlen("\r\n");
				total=buffersize+totalh;
				//printf("XXXXXXXX : %u\n", total);
				x = ((char*)realloc(buffer, sizeof(char)*total));
				memmove(x+totalh, x, buffersize);
				iheaderd.SerializeHeaders(x, &totalh);
				memcpy(x+totalh-strlen("\r\n"), "\r\n", strlen("\r\n"));
				buffer=x;
				buffersize=total;

				break;
			default:
				break;
		}
		delete chitf;
		chitf=0;


	}
	

	//printchararray(buffer, buffersize, "HELLO");


	if(buffersize==0){
		*outbuflen=0;
		return HANDLER_ZERO;
	}
	if((*outbuflen) >= buffersize){
		memcpy(outbuf, buffer, buffersize);
		*outbuflen = buffersize;
		buffersize=0;
		free(buffer);
		buffer=0;
		return HANDLER_FINISHED;
	}
	else{
		memcpy(outbuf, buffer,	(*outbuflen));
		memmove(buffer, buffer+(*outbuflen), buffersize-(*outbuflen));
		buffersize-=(*outbuflen);
		buffer=(char*)realloc(buffer, buffersize);
		return HANDLER_MORE;
	}

}


/*************************************************************************
 * Handle downstream data - mirror of HandleData
 *************************************************************************/
HANDLER_RESPONSE HTTPHandler::HandleDownstreamData(char* inbuf, int* inbuflen){
	return HandleData(inbuf, inbuflen);
}


/*************************************************************************
 * Handle downstream data - mirror of HandleData
 *************************************************************************/
void HTTPHandler::KillBuffer(){
	cleanupBuffers();
	//header_finished=0;
	//connectmethod=0;
}


/*************************************************************************
 * Check to see if there is more data or if all the data has been sent
 *************************************************************************/
HANDLER_RESPONSE HTTPHandler::CheckHandlerData(){
	if(chitf)
		return HANDLER_MORE;
	if(buffersize == 0)
		return HANDLER_ZERO;
	else
		return HANDLER_MORE;	
}


/*************************************************************************
 * Delete the handler kinda like a destructor 
 *************************************************************************/
void HTTPHandler::CleanupHandlerData(){

	ProtocolHTTPParams* php = (ProtocolHTTPParams*)param1; 
	php->capi->CloseCAP(capi_param1);

	if(host_address){
		free(host_address);
		host_address=0;
	}
	if(redirecturl!=0){
		free(redirecturl);
		redirecturl=0;
	}
	cleanupBuffers();
	if(chitf){
		delete chitf;
		chitf=0;
	}
}


/*************************************************************************
 * Delete the handlers
 *************************************************************************/
int HTTPHandler::ModifyHeaders(HeaderHandler* headerd){
	char tmps64[64];
	
	/*
	 * Upstream, headers going to the web server
	 */
	if(name==HANDLER_UPSTREAM){

		
		char tmp64[64];
		if(generate_session_id(flattenip(remoteaddr), tmp64,64)==0)
			headerd->AddHeader("Spdbmp-sessionid", tmp64);
		else
			headerd->AddHeader("Spdbmp-sessionid", "no-data");

		headerd->AddHeader("X-Forwarded-For",flattenip(remoteaddr));	

		if(!cascadeon)
			headerd->RemoveHeader("Proxy-Connection");
	
		//headerd->DisplayHeaders();
		//headerd->RemoveHeader("Proxy-Connection");
		//headerd->RemoveHeader("Referer");
		//headerd->RemoveHeader("Accept-Encoding");
	}


	/*
	 * Downstream headers going to the user
	 */
	else{
			snprintf(tmps64, 64-1, "speed %d kbps", ratelimit*8/1024);
			headerd->AddHeader( "Spdbmp-dwn-rate", tmps64);
			headerd->RemoveHeader( "Server");
	}

	return 1;
}


/*************************************************************************
 * Fill the host and port in handler by decoding headers
 *************************************************************************/
void HTTPHandler::FillHostAndPort(HeaderHandler* headerd){
	
	/*
	 *	UPSTREAM
	 */
	if(name==HANDLER_UPSTREAM){
		int tmp_hostport;
		int hostlength;
		char* colon;
		const char* tmp_host	= headerd->GetHeader("Host");
		if(tmp_host){
			hostlength=strlen(tmp_host);

			if(connectmethod)
				tmp_hostport=443;
			else
				tmp_hostport=80;

			if((colon=strstr(tmp_host, ":"))){
				hostlength=colon-tmp_host;
				tmp_hostport=atoi(colon+1);
			}

			if(host_address==0  || 
				((strcmp(tmp_host, host_address)!=0  || tmp_hostport!=host_port) && (dirtybit=1))  ){
				logf<<"HTTP: Change "<<(host_address?host_address:"NULL")<<" -> "<<tmp_host<<lend;
				//std::cout<<"Change "<<(host_address?host_address:"NULL")<<" -> "<<tmp_host<<lend;
				
				host_address = (char*)realloc(host_address, hostlength+1);
				memset(host_address, 0, hostlength+1);
				memcpy(host_address, tmp_host, hostlength);
				host_port = tmp_hostport;
				
			}
			else{
				;//No change
			}
		}
		else{
			if(host_address)
				free(host_address);
			host_address=0;
		}

		//printf("up ");headerd->DisplayHeaders();
	}



	/*
	 *	DOWNSTREAM
	 */
	else{
		//printf("down ");headerd->DisplayHeaders();

	}
}


/*************************************************************************
 * Create the request from whatever is available in buffers and header 
 *************************************************************************/
void HTTPHandler::getIntermediateRequest(){ 
	unsigned int total;
	char* buf;

	//printchararray(buffer, buffersize, "Chunk fucket");

	total=buffersize+iheaderd.SerializedHeaderSize()+strlen("\r\n");
	buf = ((char*)malloc(sizeof(char)*total));
	iheaderd.SerializeHeaders(buf, &total);

	memcpy(buf+iheaderd.SerializedHeaderSize(), "\r\n", strlen("\r\n"));
	memcpy(buf+iheaderd.SerializedHeaderSize()+strlen("\r\n"), buffer, buffersize);
	
	cleanupBuffers();

	buffersize=total;	
	buffer=buf;
}


/*************************************************************************
 * Cleanup buffers 
 *************************************************************************/
void HTTPHandler::cleanupBuffers(){
	logdhttp<<"Cleaned up buffer"<<lend;
	if(buffer){
		free(buffer);
		buffer=0;
		buffersize=0;
	}
}

const char* HTTPHandler::GetConnectionInfo(){
	return iheaderd.GetRequestLine();
}
